/*
 * Copyright (c) 2011 Junichiro Okuno
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and should not be interpreted as representing official policies, either expressed
 * or implied, of Junichiro Okuno.
 */

/**
 * TCP Server, pre-allocation scheme.
 * Each Thread waits in its accept() call for a connection; this synchronizes
 * on the serversocket when calling its accept() method.
 * @author J. Okuno
 * Reference: Darwin. I F (2002). Java Cookbook, O'Reilly
*/ 

package tcp_net;

import tcp_net.debug.DebugInfo;
import tcp_net.mail.pop3.POP3Handler;
import tcp_net.mail.smtp.SMTPHandler;

import java.net.*;
import java.io.*; 
import java.util.List;
import java.util.LinkedList;

public abstract class Server extends Thread
{	
	/**
	*	Debug information
	*/
	private final static boolean DEBUG = DebugInfo.SERVER_DEBUG;
	
	/**
	* A server socket
	*/
	protected ServerSocket servSock;
	
	/**
	* A number of server threads
	*/
	protected int no_of_threads;
	
	/**
	* A list of server handlers
	*/
	protected List<Handler> handlers;
	
	/**
	* true if the server thread is to be stopped
	*/
	private boolean stop;
	
	/**
	* A tcp port number for this server
	*/
	private int port;
	
	/** 
	* Server Constructor 
	* @param p a port number
	* @param numThreads a number of handler threads
	*/
	public Server(int p, int numThreads)
	{
		port = p;
		createServerSocket(port);
		
		no_of_threads = numThreads;
		DebugInfo.print(DEBUG,"No of threads can run is: "+no_of_threads);
		
		handlers = new LinkedList<Handler>();
		stop = false;
		this.start();
	}
	
	/**
	* This method provides to start running a server
	*/
	public abstract void startServer();
	
	/**
	* This method provides get a current port number of this server
	* @return a port number
	*/
	public int getPortNo() { return port; }
	
	/**
	* Start Server thread to monitor and manage a list of Serve handlers
	*/
	public void run()
	{
		while(!stop)
		{
			if(handlers.size()>0)
			{
				for(int i=0; i < handlers.size(); i++)
				{
					displayHandlerStatus(handlers.get(i));
					
					if(handlers.get(i).closed())
					{
						DebugInfo.print(DEBUG,"Server: Handler "+ handlers.get(i).getThreadNumber() +" is to be removed.");
						
						handlers.get(i).stopThread();
						handlers.remove(handlers.get(i));
					}
				}
			}
			
			try 
			{ 
				DebugInfo.print(DEBUG,"Server Sleeping.");
				Thread.sleep(30000); 
			}
			
			catch(Exception ex) 
			{
				DebugInfo.print(DEBUG,"Server: Thread Error");
			}
		}
	}
	
	/**
	* This method provides to return a list of this server's handlers
	* @return a list of Server Handlers
	*/
	public synchronized List<Handler> getHandlers()
	{
		return handlers;
	}
	
	/**
	* This method provides to start a server's new hander
	* @param hdl a Server Handler
	* @return an index of the added handler in the list
	*/
	public synchronized int runNewHandler(Handler hdl)
	{
		hdl.start();
		handlers.add(hdl);
		
		return handlers.size()-1;
	}
	
	/**
	* This method provides to remove a server's handler from the list
	* @param index an index of Server Handler
	* @return true if the handler is removed
	*/
	public synchronized boolean removeHandler(int index)
	{
		if(handlers.size() < index)
		{
			handlers.remove(index);
			return true;
		}
		else
		{
			return false;
		}
	}
	
	/**
	* This method provides to terminate a connection between the server and its clients
	*/
	public synchronized void endServer()
	{
		for(Handler h : handlers)
		{
			h.setCloseConnection();
		}
		handlers.clear();
		
		try
		{
			if(servSock != null) 
			{ 
				servSock.close(); 
				servSock = null;
			}
		}
		catch(Exception ex) {}
		
		stop = true;
	}
	
	/**
	* This method provides to create a new server socket
	* @param port a server port number
	*/
	protected void createServerSocket(int port)
	{
		try {
			DebugInfo.print(DEBUG, "Server socket created. Port: "+port);
			servSock = new ServerSocket(port);
			
		} catch(IOException e) {
			/* Crash the server if IO fails. Something bad has happened */
			DebugInfo.print(DEBUG,"Exception: Could not create ServerSocket " + e);
			
			System.exit(1);
			return;	/*NOTREACHED*/
		}
	}
	
	/**
	* Run Server Handlers by creating a series of threads and start them.
	* @param hnd a list of Server Handlers
	*/
	protected synchronized void runHandlers(List<Handler> hnd)
	{
		handlers = hnd;
		
		for (int i=0; i< no_of_threads; i++) 
		{ 
			DebugInfo.print(DEBUG, "Server: Handler "+i+" started.");
			handlers.get(i).start(); 
		}
	}
	
	/**
	* This method provides to display the server handlers status (For Debugging)
	* @param h a Server Handler
	*/
	private void displayHandlerStatus(Handler h)
	{
		String type = "Handler";
		if(h instanceof POP3Handler) { type = "POP3Handler"; }
		else if(h instanceof POP3Handler) { type = "SMTPHandler"; }
		DebugInfo.print(DEBUG,type+" Thread Number: "+h.getThreadNumber()+" started: "+h.started()+" closed: "+h.closed());
	}
}
